package uk.co.davedexter.flickruploader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import com.aetrion.flickr.Flickr;
import com.aetrion.flickr.FlickrException;
import com.aetrion.flickr.REST;
import com.aetrion.flickr.RequestContext;
import com.aetrion.flickr.auth.Auth;
import com.aetrion.flickr.auth.AuthInterface;
import com.aetrion.flickr.auth.Permission;
import com.aetrion.flickr.photos.Photo;
import com.aetrion.flickr.photos.PhotoList;
import com.aetrion.flickr.photos.PhotosInterface;
import com.aetrion.flickr.photos.SearchParameters;
import com.aetrion.flickr.uploader.UploadMetaData;
import com.aetrion.flickr.uploader.Uploader;
import com.aetrion.flickr.util.FileAuthStore;

public class FlickrUploader2 {

	private long start;
	private File topLevelPhotoDir;
	private Connection conn;
	private Statement s;
	private ResultSet results;

	private ArrayList<File> uncertain;

	private Flickr flickr;
	private FileAuthStore store;
	AuthInterface authInterface;
	Auth auth;
	private static String apiKey = "69d692ab74bf18622850333aa14e4ee0";
	private static String secret = "ab48698ad2813b7e";
	private Uploader up;

	FlickrUploader2(String topLevel) {
		start = System.currentTimeMillis();
		uncertain = new ArrayList<File>();

		// Check the top level directory
		System.out.println("Checking the directory exists.");
		topLevelPhotoDir = new File(topLevel);
		if ((!topLevelPhotoDir.exists()) | (!topLevelPhotoDir.isDirectory())) {
			System.err.println("The directory does not exist: " + topLevelPhotoDir);
			System.exit(-1);
		}

		// Check the database
		try {
			// load the embedded driver
			Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
			conn = DriverManager.getConnection("jdbc:derby:FlickrPhotos");

		} catch (SQLException se) {
			String cause = se.getCause().toString();
			if (cause.contains("Database 'FlickrPhotos' not found.")) {
				System.out.println("Appears to be the first time run. Creating database - please wait.");
				try {
					conn = DriverManager.getConnection("jdbc:derby:FlickrPhotos;create=true");
					s = conn.createStatement();
					s.execute("create table photos(file varchar(255), uploaded varchar(5), search_count int)");
				} catch (SQLException se1) {
					se1.printStackTrace();
				}
				cleanUp();
				System.out.println("Please run again.");

				System.exit(0);

			} else {
				se.printStackTrace();
				cleanUp();
			}
		} catch (InstantiationException ie) {
			ie.printStackTrace();
			cleanUp();
		} catch (IllegalAccessException iae) {
			iae.printStackTrace();
			cleanUp();
		} catch (ClassNotFoundException cnfe) {
			cnfe.printStackTrace();
			cleanUp();
		}

		// Set up the flickr connection
		try {
			flickr = new Flickr(apiKey, secret, new REST());
			authInterface = flickr.getAuthInterface();

			RequestContext rc = RequestContext.getRequestContext();
			up = new Uploader(apiKey, secret);

			File authDir = new File("authstore");
			if (authDir.exists() & authDir.isDirectory()) {
				System.out.println("Loading existing authorisation.");
				store = new FileAuthStore(authDir);
				auth = store.retrieve("47213205@N04");
				rc.setAuth(auth);
			} else {
				// Prompt the user.
				String frob = authInterface.getFrob();
				URL url = authInterface.buildAuthenticationUrl(Permission.WRITE, frob);
				System.out.println("Press return after you granted access at this URL:");
				System.out.println(url.toExternalForm());
				BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
				infile.readLine();
				auth = authInterface.getToken(frob);

				rc.setAuth(auth);

				store = new FileAuthStore(new File("authstore"));
				store.store(auth);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (FlickrException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
	}

	private void readPhotoFiles(File dir) {
		if (dir.isDirectory()) {
			String[] children = dir.list();
			for (int i = 0; i < children.length; i++) {
				readPhotoFiles(new File(dir, children[i]));
			}
		} else {
			String filename = dir.getName();
			String ext = (filename.lastIndexOf(".") == -1) ? "" : filename.substring(filename.lastIndexOf(".") + 1,
					filename.length());
			if (ext.toLowerCase().equals("jpg") | ext.toLowerCase().equals("jpeg")) {
				String[] fileParts = dir.toString().replace(topLevelPhotoDir.toString() + File.separator, "")
						.split(File.separator);
				String tags = "";
				for (int i = 0; i < fileParts.length - 1; i++) {
					tags += fileParts[i].replace(" - ", ": ").replace("-", ".") + ";";
				}
				tags = tags.substring(0, tags.length() - 1);
				// System.out.println(dir);
				// System.out.println(tags);
				processImage(dir, tags);

			}
		}
	}

	private void processImageDirectory() {
		readPhotoFiles(topLevelPhotoDir);
		System.out.printf("\n\nFiles with a search count greater than 1:\n");
		for (File file : uncertain) {
			System.out.println("\t" + file);
		}
	}

	private int processImage(File file, String tags) {
		if (file.toString().contains("Picasa3Temp")) {
			System.out.println("Ignoring " + file);
			return(0);
		}
		
		try {
			s = conn.createStatement();
			System.out.println("Processing " + file);

			// Check if the database tells us to upload.
			System.out.printf("\tChecking database to see if %s is already uploaded.\n", file);
			results = s.executeQuery("SELECT uploaded FROM photos WHERE file = '"
					+ file.toString().replaceAll("'", "''") + "'");
			if (results.next()) {
				String uploaded = results.getString("uploaded");
				System.out.println("\t\tFound in database with uploaded value: " + uploaded);
				if (uploaded.equals("true")) {
					System.out.println("\t\tAlready uploaded.");
					return (0);
				} else {
					System.out.println("\t\tFound in database but has not been uploaded. Possible after a failed upload.");
				}
			} else {
				System.out.println("\t\tNot found in the database.");
			}

			// Search flickr to see how many images are uploaded with the name
			// and tags.
			System.out.println("\tSearching flickr to see if how many images are there with the same name and tags.");
			int searchCount = searchFlickr(file.getName(), tags);

			// Upload if search count is zero, skip if search count is 1 and add
			// to a list of uncertain files if > 1
			switch (searchCount) {
			case 0:
				System.out.println("\t\tSearch count is 0 so uploading and adding to database.");
				boolean success = upload(file);
				if (success) {
					s.execute("INSERT INTO photos (file, uploaded, search_count) VALUES ('"
						+ file.toString().replace("'", "''") + "', 'true', 1)");
				} else {
					s.execute("INSERT INTO photos (file, uploaded, search_count) VALUES ('"
							+ file.toString().replace("'", "''") + "', 'true', 0)");
				}
				break;
			case 1:
				System.out.println("\t\tSearch count is 1 so already uploaded. Adding to database but not uploading.");
				s.execute("INSERT INTO photos (file, uploaded, search_count) VALUES ('"
						+ file.toString().replace("'", "''") + "', 'true', 1)");
				break;
			default:
				System.out.println("\t\tSearch count is greater than 1 so adding to uncertain list.");
				uncertain.add(file);
				s.execute("INSERT INTO photos (file, uploaded, search_count) VALUES ('"
						+ file.toString().replace("'", "''") + "', 'true', " + searchCount + ")");
				break;
			}

		} catch (SQLException e) {
			e.printStackTrace();
		}
		return (0);
	}

	private int searchFlickr(String name, String tags) {
		int searchCount = 999;

		try {
			PhotosInterface photoInt = flickr.getPhotosInterface();
			SearchParameters searchParams = new SearchParameters();
			searchParams.setMedia("photos");
			System.out.printf("\t\tSearching flickr with name: %s and tags: %s\n", name, tags.toLowerCase().replaceAll("[ .,:'@-_=+)(*&^%$£!`]", "").replace(";", " "));
			searchParams.setText("\"" + name + "\" "
					+ tags.toLowerCase().replaceAll("[ .,:'@-_=+)(*&^%$£!`]", "").replace(";", " "));
			searchParams.setUserId("47213205@N04");

			PhotoList results = photoInt.search(searchParams, 50, 0);
			System.out.println("\t\tSearch count: " + results.size());
			searchCount = results.size();

		} catch (FlickrException fe) {
			fe.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}

		return (searchCount);
	}

	private boolean upload(File file) {
		// Sort the tags
		String[] tags = file.toString().replace(topLevelPhotoDir + File.separator, "")
				.replace(File.separator + file.getName(), "").split(File.separator);
		ArrayList<String> tagsArray = new ArrayList<String>();
		for (String tag : tags) {
			tagsArray.add("\"" + tag.replace(" - ", ": ").replace("-", ".") + "\"");
		}
		tagsArray.add("\"Automatically Uploaded\"");
		
		// Perform the upload
		up = flickr.getUploader();
		UploadMetaData uploadMetaData = new UploadMetaData();
		uploadMetaData.setTitle(file.getName());
		uploadMetaData.setTags(tagsArray);
		try {
			byte[] data = getBytesFromFile(file);
			String result = up.upload(data, uploadMetaData);
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (FlickrException e) {
			e.printStackTrace();
			return(false);
		} catch (SAXException e) {
			e.printStackTrace();
		}
		return (true);
	}

	public static byte[] getBytesFromFile(File file) throws IOException {
		InputStream is = new FileInputStream(file);

		// Get the size of the file
		long length = file.length();

		if (length > Integer.MAX_VALUE) {
			// File is too large
		}

		// Create the byte array to hold the data
		byte[] bytes = new byte[(int) length];

		// Read in the bytes
		int offset = 0;
		int numRead = 0;
		while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
			offset += numRead;
		}

		// Ensure all the bytes have been read in
		if (offset < bytes.length) {
			throw new IOException("Could not completely read file " + file.getName());
		}

		// Close the input stream and return bytes
		is.close();
		return bytes;
	}

	private void cleanUp() {
		// Close the connection
		try {
			// the shutdown=true attribute shuts down Derby
			DriverManager.getConnection("jdbc:derby:FlickrPhotos;shutdown=true");
			// To shut down a specific database only, but keep the
			// engine running (for example for connecting to other
			// databases), specify a database in the connection URL:
			// DriverManager.getConnection("jdbc:derby:" + dbName +
			// ";shutdown=true");
		} catch (SQLException se) {
			if (((se.getErrorCode() == 50000) && ("XJ015".equals(se.getSQLState())))) {
				// we got the expected exception
				System.out.println("Derby shut down normally");
				// Note that for single database shutdown, the expected
				// SQL state is "08006", and the error code is 45000.
			} else if (((se.getErrorCode() == 45000) && ("08006".equals(se.getSQLState())))) {
				System.out.println("Derby single connection to db shut down normally");
			} else {
				System.err.println("Derby did not shut down normally");
				se.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		// Get the arguments
		if (args.length != 1) {
			System.err.println("Incorrect arguments.\nUsage: java -jar flickruploader.jar <directoryOfPhotos>");
			System.exit(-1);
		}
		FlickrUploader2 uploader = new FlickrUploader2(args[0]);
		uploader.processImageDirectory();

		uploader.cleanUp();
	}

}
